package org.hawk.gwt.ppc.mojo;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;

/**
 * Invokes GWT preprocessor for java sources.
 * 
 * @author alex.bereznevatiy@gmail.com
 *
 * @goal preprocess
 * 
 * @phase process-sources
 */
public class GWTProcessorMojo extends AbstractMojo {
	
	private static final String PREPROCESSOR_VERSION = "0.6";
	
	/**
     * Output directory location.
     * @parameter expression="${project.build.directory}"
     * @required
     * @readonly
     */
    private File outputDirectory;
    
    /**
     * The project to be preprocessed.
     *
     * @parameter default-value="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;
    
    /**
     * Factory to create artifacts.
     * 
     * @parameter expression = "${component.org.apache.maven.artifact.factory.ArtifactFactory}"
     * @required
     * @readonly
     */
    protected ArtifactFactory factory;
    
    /**
     * Used to look up Artifacts in the remote repository.
     * 
     * @parameter expression = "${component.org.apache.maven.artifact.resolver.ArtifactResolver}"
     * @required
     * @readonly
     */
    protected ArtifactResolver artifactResolver;
    
    /**
     * List of remote repositories
     * 
     * @parameter expression="${project.remoteArtifactRepositories}"
     * @required
     * @readonly
     */
    protected List<?> remoteRepositories;
    
    /**
     * Location of the local repository.
     * 
     * @parameter expression="${localRepository}"
     * @required
     * @readonly
     */
    protected ArtifactRepository localRepository;
    
    /**
     * Reflection pattern to match resources to reflect.
     * 
     * @parameter
     */
    protected String reflect;
    
    /**
     * Type of pattern specified in {@link #reflect} parameter.
     * 
     * @parameter
     * @alias pattern
     */
    protected String pattern;
    
    /**
     * Flag to indicate should GWT preprocessor also process Java generics.
     * 
     * @parameter
     */
    protected Boolean useGenerics;
    
    /**
     * Flag to indicate should GWT preprocessor also process Java annotations.
     * 
     * @parameter
     */
    protected Boolean processAnnotations;
    
    /**
     * List of modules to process.
     * 
     * @parameter
     */
    protected String[] modules;
    
    /**
     * Shorthand for {@link #modules} if only one module should be processed.
     * 
     * @parameter
     */
    protected String module;
    
    
    private void setPreprocessedAsSourceRoot(String target) throws MojoExecutionException {
    	if (target == null) {
    		throw new MojoExecutionException("Internal error! No output dir is available.");
    	}
            
        @SuppressWarnings("unchecked")
		final List<String> sources = project.getCompileSourceRoots();
        
        for (String src : sources) {
        	try {
        		copyWithoutOverride(new File(src), new File(target));
        	} catch (IOException ex) {
        		throw new MojoExecutionException(ex.toString());
        	}
        }
        
        sources.clear();
        sources.add(target);
    }
    
    /*
     * Copy everything not processed to the output directory to be compiled if necessary.
     */
	private void copyWithoutOverride(File src, File target) throws IOException {
		if (src.isFile()) {
			if (!target.exists()) {
				copyFile(src, target);
			}
			return;
		}
		if (!target.exists()) {
			target.mkdirs();
		}
		for (String child : src.list()) {
			if (child.equals(".") || child.equals("..")) {
				continue;
			}
			copyWithoutOverride(new File(src, child), new File(target, child));
		}
	}

	private void copyFile(File src, File target) throws IOException {
		BufferedInputStream in = null;
		BufferedOutputStream out = null;
		
		try {
			in = new BufferedInputStream(new FileInputStream(src));
			out = new BufferedOutputStream(new FileOutputStream(target));
			byte[] buf = new byte[4096];
			
			int size;
			while ((size = in.read(buf)) > 0) {
				out.write(buf, 0, size);
			}
			out.flush();
		} finally {
			if (in != null) in.close();
			if (out != null) out.close();
		}
	}

	public void execute() throws MojoExecutionException, MojoFailureException {
		if (project == null) {
        	throw new MojoExecutionException("Internal error! Project is NULL.");
        }
		
		getLog().info("Resolving GWT preprocessor (v" + PREPROCESSOR_VERSION + ") binaries.");
		Artifact pomArtifact = resolveGWTPreprocessor();
		
		PreprocessorInvoker invoker = new PreprocessorInvoker(pomArtifact.getFile(), getClassPath(), getLog());
		
		invoker.setModules(getModulesList());
		invoker.setOutputDirectory(outputDirectory);
		invoker.setReflectionRegex(reflect);
		invoker.setRegexType(pattern);
		invoker.setUseGenerics(useGenerics);
		invoker.setProcessAnnotations(processAnnotations);
		
		setPreprocessedAsSourceRoot(invoker.invoke());
	}

	@SuppressWarnings("unchecked")
	private List<String> getClassPath() {
		List<String> allSources = new LinkedList<String>();
		
		allSources.addAll(project.getCompileSourceRoots());
		
		for (Object obj : project.getResources()) {
			Resource resource = (Resource)obj;
			allSources.add(resource.getDirectory());
		}
		
		List<Dependency> dependencies = project.getDependencies();
		for (Dependency dependency : dependencies) {
			Artifact artifact = this.factory.createArtifactWithClassifier(
					dependency.getGroupId(), 
					dependency.getArtifactId(), 
					dependency.getVersion(), 
					dependency.getType(), 
					dependency.getClassifier());
			
			if (artifact.getFile() == null) {
				tryResolve(artifact, Collections.emptyList());
			}

			allSources.add(artifact.getFile().getAbsolutePath());
		}
		
		return allSources;
	}

	private Artifact resolveGWTPreprocessor() throws MojoFailureException {
		Artifact pomArtifact = this.factory.createArtifact("org.hawk.gwt.ppc", "gwt-preprocessor", PREPROCESSOR_VERSION, "", "jar");

		tryResolve(pomArtifact, this.remoteRepositories);
		if (!pomArtifact.getFile().exists()) {
			throw new MojoFailureException("Unable to resolve GWT preprocessor " + PREPROCESSOR_VERSION);
		}
		return pomArtifact;
	}

	private void tryResolve(Artifact pomArtifact, List<?> repositories) {
		try {
			artifactResolver.resolve(pomArtifact, repositories, this.localRepository);
		} catch (ArtifactNotFoundException ex) {
			getLog().warn("Unable to resolve artifact: " + pomArtifact.getFile());
		} catch (ArtifactResolutionException ex) {
			getLog().warn("Unable to resolve artifact: " + pomArtifact.getFile());
		}
	}

	private List<String> getModulesList() throws MojoExecutionException {
		List<String> result = new ArrayList<String>();
		if (modules != null) {
			for (String module : modules) {
				if (module == null || module.trim().isEmpty()) {
					throw new MojoExecutionException("Empty module name for GWT preprocessor plugin.");
				}
				result.add(module);
			}
		}
		if (module != null) {
			if (module.trim().isEmpty()) {
				throw new MojoExecutionException("Empty module name for GWT preprocessor plugin.");
			}
			result.add(module);
		}
		if (result.isEmpty())  {
			throw new MojoExecutionException("At least one module should be specified for GWT preprocessor plugin.");
		}
		return Collections.unmodifiableList(result);
	}
}
